/*   Copyright (C) 2008  Jeroen v. Schagen & Youri Lima
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.IO;
using System.Drawing;

using Engine.Math;

namespace Engine.Rendering
{
    // Terrain
    public class Terrain : IRenderable
    {
        private String _name;
        private String _file;
        private TerrainType _type;

        private Microsoft.DirectX.Direct3D.Mesh _mesh;
        private Microsoft.DirectX.Direct3D.IndexBuffer _ib;
        private Microsoft.DirectX.Direct3D.VertexBuffer _vb;
        private Microsoft.DirectX.Direct3D.CustomVertex.PositionColored[] _vertices;

        private short[] _indices;
        private int[,] _heightdata;
        private int height, width;
        private int maxheight = 1;
        private int MinimumHeight = 255;
        private int MaximumHeight = 0;

        public Terrain(string name, string file, TerrainType type)
        {
            _name = name;
            _file = file;
            _type = type;
        }

        public bool LoadRAW(string file, int w, int h)
        {
            height = h;
            width = w;

            _heightdata = new int[width, height];

            FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
            BinaryReader r = new BinaryReader(fs);

            for (int i = 0; i < height; i++)
            {
                for (int y = 0; y < width; y++)
                {
                    int h2 = (int)(r.ReadByte() / (byte.MaxValue / maxheight));
                    _heightdata[y, i] = h2;

                    if (h2 < MinimumHeight)
                    {
                        MinimumHeight = h2;
                    }

                    if (h2 > MaximumHeight)
                    {
                        MaximumHeight = h2;
                    }
                }
            }

            r.Close();

            return true;
        }

        public bool LoadBMP(string file)
        {
            int offset;
            byte dummy;

            FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
            BinaryReader r = new BinaryReader(fs);

            for (int i = 0; i < 10; i++)
            {
                dummy = r.ReadByte();
            }

            offset = r.ReadByte();
            offset += r.ReadByte() * 256;
            offset += r.ReadByte() * 256 * 256;
            offset += r.ReadByte() * 256 * 256 * 256;

            for (int i = 0; i < 4; i++)
            {
                dummy = r.ReadByte();
            }

            width = r.ReadByte();
            width += r.ReadByte() * 256;
            width += r.ReadByte() * 256 * 256;
            width += r.ReadByte() * 256 * 256 * 256;

            height = r.ReadByte();
            height += r.ReadByte() * 256;
            height += r.ReadByte() * 256 * 256;
            height += r.ReadByte() * 256 * 256 * 256;

            _heightdata = new int[width, height];

            for (int i = 0; i < (offset - 26); i++)
            {
                dummy = r.ReadByte();
            }

            for (int i = 0; i < height; i++)
            {

                for (int y = 0; y < width; y++)
                {
                    int h = (int)(r.ReadByte());
                    h += (int)(r.ReadByte());
                    h += (int)(r.ReadByte());
                    h /= ((256+256+256) / maxheight);
                    _heightdata[width - 1 - y, height - 1 - i] = h;

                    if (h < MinimumHeight)
                    {
                        MinimumHeight = h;
                    }

                    if (h > MaximumHeight)
                    {
                        MaximumHeight = h;
                    }
                }
            }

            return true;
        }

        public bool LoadPNG(string file)
        {
            return true;
        }

        public bool LoadMesh(string file)
        { 
            return true; 
        }

        private void VertexDeclaration(Microsoft.DirectX.Direct3D.Device device)
        {
            _vb = new Microsoft.DirectX.Direct3D.VertexBuffer(typeof(Microsoft.DirectX.Direct3D.CustomVertex.PositionColored), width * height, device, Microsoft.DirectX.Direct3D.Usage.Dynamic | Microsoft.DirectX.Direct3D.Usage.WriteOnly, Microsoft.DirectX.Direct3D.CustomVertex.PositionColored.Format, Microsoft.DirectX.Direct3D.Pool.Default);
            _vertices = new Microsoft.DirectX.Direct3D.CustomVertex.PositionColored[width * height];

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    _vertices[x + y * width].Position = new Microsoft.DirectX.Vector3(x, _heightdata[x, y], y);

                    if (_heightdata[x, y] < MinimumHeight + ((MaximumHeight - MinimumHeight) / 4))
                    {
                        _vertices[x + y * width].Color = Color.Blue.ToArgb();
                    }

                    else if (_heightdata[x, y] < MinimumHeight + (((MaximumHeight - MinimumHeight) * 2) / 4))
                    {
                        _vertices[x + y * width].Color = Color.Green.ToArgb();
                    }

                    else if (_heightdata[x, y] < MinimumHeight + (((MaximumHeight - MinimumHeight) * 3) / 4))
                    {
                        _vertices[x + y * width].Color = Color.Brown.ToArgb();
                    }

                    else
                    {
                        _vertices[x + y * width].Color = Color.White.ToArgb();
                    }
                }
            }

            _vb.SetData(_vertices, 0, Microsoft.DirectX.Direct3D.LockFlags.None);
        }

        private void IndicesDeclaration(Microsoft.DirectX.Direct3D.Device device)
        {
            _ib = new Microsoft.DirectX.Direct3D.IndexBuffer(typeof(short), (width - 1) * (height - 1) * 6, device, Microsoft.DirectX.Direct3D.Usage.WriteOnly, Microsoft.DirectX.Direct3D.Pool.Default);
            _indices = new short[(width - 1) * (height - 1) * 6];

            for (int x = 0; x < width - 1; x++)
            {
                for (int y = 0; y < height - 1; y++)
                {
                    _indices[(x + y * (width - 1)) * 6]      = (short)((x + 1) + (y + 1) * width);
                    _indices[(x + y * (width - 1)) * 6 + 1]  = (short)((x + 1) + y * width);
                    _indices[(x + y * (width - 1)) * 6 + 2]  = (short)(x + y * width);

                    _indices[(x + y * (width - 1)) * 6 + 3]  = (short)((x + 1) + (y + 1) * width);
                    _indices[(x + y * (width - 1)) * 6 + 4]  = (short)(x + y * width);
                    _indices[(x + y * (width - 1)) * 6 + 5]  = (short)(x + (y + 1) * width); 
                }
            }

            _ib.SetData(_indices, 0, Microsoft.DirectX.Direct3D.LockFlags.None);
        }

        private void CreateMesh(Microsoft.DirectX.Direct3D.Device device)
        {
            _mesh = new Microsoft.DirectX.Direct3D.Mesh((width - 1) * (height - 1) * 2, width * height, Microsoft.DirectX.Direct3D.MeshFlags.Managed, Microsoft.DirectX.Direct3D.CustomVertex.PositionColored.Format, device);

            _mesh.SetVertexBufferData(_vertices, Microsoft.DirectX.Direct3D.LockFlags.None);
            _mesh.SetIndexBufferData(_indices, Microsoft.DirectX.Direct3D.LockFlags.None);

            int[] adjac = new int[_mesh.NumberFaces * 3];
            _mesh.GenerateAdjacency(0.5f, adjac);
            _mesh.OptimizeInPlace(Microsoft.DirectX.Direct3D.MeshFlags.OptimizeVertexCache, adjac);

            _mesh = _mesh.Clone(_mesh.Options.Value, Microsoft.DirectX.Direct3D.CustomVertex.PositionNormalColored.Format, device);
            _mesh.ComputeNormals();
        }

        public Terrain Clone()
        {
            Terrain clone = new Terrain(_name, _file, _type);

            clone.Mesh = _mesh.Clone(Microsoft.DirectX.Direct3D.MeshFlags.Managed, Microsoft.DirectX.Direct3D.CustomVertex.PositionNormalColored.Format, _mesh.Device);
            clone.Mesh.ComputeNormals();

            return clone;
        }

        public void Init(Microsoft.DirectX.Direct3D.Device device)
        {
            switch (_type)
            {
                case TerrainType.TP_RAW:
                    this.LoadRAW(_file, 100, 100);
                    break;

                case TerrainType.TP_BMP:
                    this.LoadBMP(_file);
                    break;

                case TerrainType.TP_PNG:
                    this.LoadPNG(_file);
                    break;

                case TerrainType.TP_MESH:
                    this.LoadMesh(_file);
                    break;
            }

            VertexDeclaration(device);
            IndicesDeclaration(device);

            CreateMesh(device);
        }

        public void Render(Microsoft.DirectX.Direct3D.Device device, Camera camera)
        {
            int numSubSets = _mesh.GetAttributeTable().Length;

            device.VertexFormat = Microsoft.DirectX.Direct3D.CustomVertex.PositionColored.Format;
            device.Material = default(Microsoft.DirectX.Direct3D.Material);
            device.SetTexture(0, null);
            device.Transform.World = Microsoft.DirectX.Matrix.Identity * Microsoft.DirectX.Matrix.Scaling(1, 1, 1);

            for (int i = 0; i < numSubSets; ++i)
            {
                _mesh.DrawSubset(i);
            }
        }

        public float Intersects(Ray r)
        {
            try
            {
                Vector3 pos = r.Origin;

                int point1, point2, point3, point4;
                point1 = (int) System.Math.Floor(pos.x);
                point2 = (int) System.Math.Ceiling(pos.x);
                if (point1 == point2) point2++;
                point3 = (int) System.Math.Floor(pos.z);
                point4 = (int) System.Math.Ceiling(pos.z);
                if (point3 == point4) point4++;

                Vector3 pos1 = new Vector3(point1, _heightdata[point1, point3], point3);
                Vector3 pos2 = new Vector3(point1, _heightdata[point1, point4], point4);
                Vector3 pos3 = new Vector3(point2, _heightdata[point2, point3], point3);
                Vector3 pos4 = new Vector3(point2, _heightdata[point2, point4], point4);

                Plane p1 = new Plane(pos1, pos2, pos4);
                Plane p2 = new Plane(pos1, pos2, pos4);

                float inters1 = r.Intersects(p1);
                float inters2 = r.Intersects(p2);

                return (inters1 + inters2) / 2.0f;
            }

            catch (Exception)
            {
                return 0;
            }
        }

        // Acessors
        public String Name
        {
            get { return _name; }
        }

        public Microsoft.DirectX.Direct3D.Mesh Mesh
        {
            get { return _mesh; }
            set { _mesh = value; }
        }

        public int Maxheight
        {
            get { return maxheight; }
            set { maxheight = value; }
        }
    }
}